#
# Copyright 2012 ibiblio
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0.txt
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import pika
from rabbitmq_connector import RabbitMQConnector, RabbitMQConnectorException, DEFAULT_CONTENT_TYPE, DELIVERY_TRANSIENT
import socket
import time

import seedbank.messaging.log_helper as log_helper

class RabbitMQPublisher(RabbitMQConnector):
    def __init__(self, **kwargs):
        RabbitMQConnector.__init__(self, **kwargs)
        self._content_type = kwargs.get('content_type', DEFAULT_CONTENT_TYPE)
        self._delivery_mode = int(kwargs.get('delivery_mode', DELIVERY_TRANSIENT))
        self._run_purge_queue = bool(kwargs.get('purge_queue', False))
        self._got_publish_return = False

    def _preflight_check(self):
        RabbitMQConnector._preflight_check(self)
        if not self._routing_key:
            raise ValueError('Missing routing key')

    def _on_channel_open_stage2(self):
        self._queue_name = self._routing_key
        #self._declare_queue(self.on_queue_declared)
        self.on_queue_declared(None)

    def on_queue_declared(self, frame):
        self._log.info('Queue declared (%s)' % (self._queue_name))
        if self._run_purge_queue:
            self._purge_queue()
        else:
            #self._channel.add_on_return_callback(self.on_publish_return)
            self._set_ready()

    def _purge_queue(self):
        if self._verbose:
            self._log.info('Calling queue_purge (queue: %s)' % (self._queue_name))
        self._channel.queue_purge(queue=self._queue_name, callback=self.on_queue_purged)

    def on_queue_purged(self, frame):
        self._log.info('Queue purged (%s)' % (self._queue_name))
        self._set_ready()

    def publish(self, message):
        #arguments = {'x-message-ttl': 180000,
        #             'x-expires': 180000}
        if not self._channel:
            raise RabbitMQConnectorException('Cannot publish message without open channel')

        if not self._connection_is_open():
            raise RabbitMQConnectorException('Cannot publish message when connection is not open')

        if self._verbose:
            self._log.info('Publishing message (exchange: %s, routing key: %s, delivery mode: %i)' %
                           (self._exchange, self._routing_key, self._delivery_mode))
            self._log.info('content type: %s' % self._content_type)
            self._log.info('message: %s' % message)

        properties = pika.BasicProperties(content_type=self._content_type,
                                          delivery_mode=self._delivery_mode
                                          )
        try:
            self._channel.basic_publish(exchange=self._exchange,
                                        routing_key=self._routing_key,
                                        properties=properties,
                                        body=message)
            #self._wait_for_publish_return()

        except socket.error, e:
            self._log.error('Socket error publishing message: %s', str(e))
        except Exception, e:
            self._log.error('Exception publishing message: %s', str(e))

    def _wait_for_publish_return(self):
        stop = 60
        count = 0
        while not self._got_publish_return and not (count > stop):
            time.sleep(.5)
            count += 1
        self._got_publish_return = False

    def on_publish_return(self, frame):
        self._got_publish_return = True

class SelfManagingRabbitMQPublisher(object):
    def __init__(self, **kwargs):
        self._wait_cycles = 0
        self._wait_interval = 0.2 # seconds
        self._wait_timeout = 2 # seconds
        self._timed_out = False
        self._kwargs = kwargs # needed for creating rabbitmq publisher objects
        self._publisher = None
        self._publisher_klass = RabbitMQPublisher
        self._log = log_helper.get_logger(self.__class__.__name__)
        self._verbose = kwargs.get('verbose', False)

    def publish(self, message):
        self._check_or_create_publisher()
        if not self._publisher.connected():
            self._wait_for_connected()
        if self._publisher.connected():
            try:
                self._publisher.publish(message)
            except RabbitMQConnectorException, e:
                self._log.error(str(e))
        else:
            self._log.error('Unable to send message.  MQPublisher not connected.')

    def stop(self):
        if self._publisher:
            self._publisher.stop()
            self._publisher.join()

    def _check_or_create_publisher(self):
        # Need to recycle a publisher that has been disconnected from rabbitmq
        if self._publisher and not self._publisher.is_alive():
            self._publisher = None

        if not self._publisher:
            self._publisher = self._create_publisher(**self._kwargs)
            if self._verbose:
                self._log.info('Calling publisher.start()')
            self._publisher.start()

    def _create_publisher(self, **kwargs):
        if not kwargs.has_key('config'):
            raise RabbitMQConnectorException('Missing config')
        publisher = self._publisher_klass(**kwargs)
        return publisher

    def _wait_for_connected(self):
        """
        Rabbitmq connection is async.  Need to look for a valid connection.
        """
        if self._verbose:
            self._log.info('Waiting for MQPublisher to connect')
        # We don't want messages to pile up.  Only wait for a
        # connection once.
        if self._timed_out:
            return
        stop = int(self._wait_timeout/self._wait_interval)
        while not self._publisher.connected():
            if self._wait_cycles >= stop:
                self._timed_out = True
                break
            time.sleep(self._wait_interval)
            self._wait_cycles += 1
